using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

namespace SQLServerFramework
{
    public partial class UtilityRegex
    {
        /// <summary>
        /// SLOW version -- accepts NVARCHAR of any size : input/output types are NVARCHAR(MAX).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        [return: SqlFacet(MaxSize = -1)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_is_match"
        )]
        public static SqlBoolean RegexIsMatch(
            [SqlFacet(MaxSize = -1)]SqlString input
            , [SqlFacet(MaxSize = -1)]SqlString pattern)
        {
            return (SqlBoolean)Regex.Match(input.Value, pattern.Value).Success;
        }

        /// <summary>
        /// SLOW version -- accepts NVARCHAR of any size : input/output types are NVARCHAR(MAX).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        [return: SqlFacet(MaxSize = -1)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_is_match_singleline"
        )]
        public static SqlBoolean RegexIsMatchSingleline(
            [SqlFacet(MaxSize = -1)]SqlString input
            , [SqlFacet(MaxSize = -1)]SqlString pattern)
        {
            return (SqlBoolean)Regex.Match(input.Value, pattern.Value, RegexOptions.Singleline).Success;
        }

        /// <summary>
        /// FAST version -- accepts NVARCHAR up to 4000 : input/output types are NVARCHAR(4000).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        [return: SqlFacet(MaxSize = 4000)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_is_match_4000"
        )]
        public static SqlBoolean RegexIsMatch4000(
            [SqlFacet(MaxSize = 4000)]SqlString input
            , [SqlFacet(MaxSize = 4000)]SqlString pattern)
        {
            return (SqlBoolean)Regex.Match(input.Value, pattern.Value).Success;
        }

        /// <summary>
        /// FAST version -- accepts NVARCHAR up to 4000 : input/output types are NVARCHAR(4000).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        [return: SqlFacet(MaxSize = 4000)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_is_match_singleline_4000"
        )]
        public static SqlBoolean RegexIsMatchSingleline4000(
            [SqlFacet(MaxSize = 4000)]SqlString input
            , [SqlFacet(MaxSize = 4000)]SqlString pattern)
        {
            return (SqlBoolean)Regex.Match(input.Value, pattern.Value, RegexOptions.Singleline).Success;
        }

        /// <summary>
        /// Modified behavior of regular expression replace -- returns NULL when no match is found.
        /// SLOW version -- accepts NVARCHAR of any size : input/output types are NVARCHAR(MAX).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <param name="replacement"></param>
        /// <returns></returns>
        [return: SqlFacet(MaxSize = -1)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_replace"
        )]
        public static SqlString RegexReplace(
            [SqlFacet(MaxSize = -1)]SqlString input
            , [SqlFacet(MaxSize = -1)]SqlString pattern
            , [SqlFacet(MaxSize = -1)]SqlString replacement)
        {
            Regex r = new Regex(pattern.Value);

            return r.Match(input.Value).Success ? (SqlString)r.Replace(input.Value, replacement.Value) : SqlString.Null;
        }

        /// <summary>
        /// Modified behavior of regular expression replace -- returns NULL when no match is found.
        /// SLOW version -- accepts NVARCHAR of any size : input/output types are NVARCHAR(MAX).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <param name="replacement"></param>
        /// <returns></returns>
        [return: SqlFacet(MaxSize = -1)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_replace_singleline"
        )]
        public static SqlString RegexReplaceSingleline(
            [SqlFacet(MaxSize = -1)]SqlString input
            , [SqlFacet(MaxSize = -1)]SqlString pattern
            , [SqlFacet(MaxSize = -1)]SqlString replacement)
        {
            Regex r = new Regex(pattern.Value, RegexOptions.Singleline);

            return r.Match(input.Value).Success ? (SqlString)r.Replace(input.Value, replacement.Value) : SqlString.Null;
        }

        /// <summary>
        /// Modified behavior of regular expression replace -- returns NULL when no match is found.
        /// FAST version -- accepts NVARCHAR up to 4000 : input/output types are NVARCHAR(4000).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <param name="replacement"></param>
        /// <returns></returns>
        [return: SqlFacet(MaxSize = 4000)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_replace_4000"
        )]
        public static SqlString RegexReplace4000(
            [SqlFacet(MaxSize = 4000)]SqlString input
            , [SqlFacet(MaxSize = 4000)]SqlString pattern
            , [SqlFacet(MaxSize = 4000)]SqlString replacement)
        {
            Regex r = new Regex(pattern.Value);

            return r.Match(input.Value).Success ? (SqlString)r.Replace(input.Value, replacement.Value) : SqlString.Null;
        }

        /// <summary>
        /// Modified behavior of regular expression replace -- returns NULL when no match is found.
        /// FAST version -- accepts NVARCHAR up to 4000 : input/output types are NVARCHAR(4000).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <param name="replacement"></param>
        /// <returns></returns>
        [return: SqlFacet(MaxSize = 4000)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_replace_singleline_4000"
        )]
        public static SqlString RegexReplaceSingleline4000(
            [SqlFacet(MaxSize = 4000)]SqlString input
            , [SqlFacet(MaxSize = 4000)]SqlString pattern
            , [SqlFacet(MaxSize = 4000)]SqlString replacement)
        {
            Regex r = new Regex(pattern.Value, RegexOptions.Singleline);

            return r.Match(input.Value).Success ? (SqlString)r.Replace(input.Value, replacement.Value) : SqlString.Null;
        }

        /// <summary>
        /// Default behavior of regular expression replace -- returns the intact input when no match is found.
        /// SLOW version -- accepts NVARCHAR of any size : input/output types are NVARCHAR(MAX).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <param name="replacement"></param>
        /// <returns></returns>
        [return: SqlFacet(MaxSize = -1)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_replace_default"
        )]
        public static SqlString RegexReplaceDefault(
            [SqlFacet(MaxSize = -1)]SqlString input
            , [SqlFacet(MaxSize = -1)]SqlString pattern
            , [SqlFacet(MaxSize = -1)]SqlString replacement)
        {
            return (SqlString)Regex.Replace(input.Value, pattern.Value, replacement.Value);
        }

        /// <summary>
        /// Default behavior of regular expression replace -- returns the intact input when no match is found.
        /// SLOW version -- accepts NVARCHAR of any size : input/output types are NVARCHAR(MAX).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <param name="replacement"></param>
        /// <returns></returns>
        [return: SqlFacet(MaxSize = -1)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_replace_default_singleline"
        )]
        public static SqlString RegexReplaceDefaultSingleline(
            [SqlFacet(MaxSize = -1)]SqlString input
            , [SqlFacet(MaxSize = -1)]SqlString pattern
            , [SqlFacet(MaxSize = -1)]SqlString replacement)
        {
            return (SqlString)Regex.Replace(input.Value, pattern.Value, replacement.Value, RegexOptions.Singleline);
        }

        /// <summary>
        /// Default behavior of regular expression replace -- returns the intact input when no match is found.
        /// FAST version -- accepts NVARCHAR up to 4000 : input/output types are NVARCHAR(4000).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <param name="replacement"></param>
        /// <returns></returns>
        [return: SqlFacet(MaxSize = 4000)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_replace_default_4000"
        )]
        public static SqlString RegexReplaceDefault4000(
            [SqlFacet(MaxSize = 4000)]SqlString input
            , [SqlFacet(MaxSize = 4000)]SqlString pattern
            , [SqlFacet(MaxSize = 4000)]SqlString replacement)
        {
            return (SqlString)Regex.Replace(input.Value, pattern.Value, replacement.Value);
        }

        /// <summary>
        /// Default behavior of regular expression replace -- returns the intact input when no match is found.
        /// FAST version -- accepts NVARCHAR up to 4000 : input/output types are NVARCHAR(4000).
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <param name="replacement"></param>
        /// <returns></returns>
        [return: SqlFacet(MaxSize = 4000)]
        [SqlMethod(
            IsDeterministic = true
            , IsPrecise = true
            , OnNullCall = false
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_replace_default_singleline_4000"
        )]
        public static SqlString RegexReplaceDefaultSingleline4000(
            [SqlFacet(MaxSize = 4000)]SqlString input
            , [SqlFacet(MaxSize = 4000)]SqlString pattern
            , [SqlFacet(MaxSize = 4000)]SqlString replacement)
        {
            return (SqlString)Regex.Replace(input.Value, pattern.Value, replacement.Value, RegexOptions.Singleline);
        }

        /// <summary>
        /// This method returns a table of matches, groups, and captures from
        /// all occurances of the pattern string found in the input string.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        [SqlFunction(
            FillRowMethodName = "FillRowMatches"
            , IsDeterministic = true
            , IsPrecise = true
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_matches"
            , TableDefinition = "match_sequence INT, match_index INT, match_value NVARCHAR(MAX), group_sequence INT, group_index INT, group_value NVARCHAR(MAX), capture_sequence INT, capture_index INT, capture_value NVARCHAR(MAX)"
        )]
        public static IEnumerable RegexMatches(
            [SqlFacet(MaxSize = -1)]SqlString input
            , [SqlFacet(MaxSize = -1)]SqlString pattern)
        {
            return GetMatches(input.IsNull ? string.Empty : input.Value
                , pattern.IsNull ? string.Empty : pattern.Value
                , RegexOptions.None);
        }
    
        [SqlFunction(
        FillRowMethodName = "FillRowMatches"
        , IsDeterministic = true
        , IsPrecise = true
        , DataAccess = DataAccessKind.None
        , SystemDataAccess = SystemDataAccessKind.None
        , Name = "regex_matches_singleline"
        , TableDefinition = "match_sequence INT, match_index INT, match_value NVARCHAR(MAX), group_sequence INT, group_index INT, group_value NVARCHAR(MAX), capture_sequence INT, capture_index INT, capture_value NVARCHAR(MAX)"
        )]
        public static IEnumerable RegexMatchesSingleline(
            [SqlFacet(MaxSize = -1)]SqlString input
            , [SqlFacet(MaxSize = -1)]SqlString pattern)
        {
            return GetMatches(input.IsNull ? string.Empty : input.Value
                , pattern.IsNull ? string.Empty : pattern.Value
                , RegexOptions.Singleline);
        }

        [SqlFunction(
            FillRowMethodName = "FillRowMatches"
            , IsDeterministic = true
            , IsPrecise = true
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_matches_4000"
            , TableDefinition = "match_sequence INT, match_index INT, match_value NVARCHAR(4000), group_sequence INT, group_index INT, group_value NVARCHAR(4000), capture_sequence INT, capture_index INT, capture_value NVARCHAR(4000)"
        )]
        public static IEnumerable RegexMatches4000(
            [SqlFacet(MaxSize = 4000)]SqlString input
            , [SqlFacet(MaxSize = 4000)]SqlString pattern)
        {
            return GetMatches(input.IsNull ? string.Empty : input.Value
                , pattern.IsNull ? string.Empty : pattern.Value
                , RegexOptions.None);
        }

        [SqlFunction(
            FillRowMethodName = "FillRowMatches"
            , IsDeterministic = true
            , IsPrecise = true
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_matches_singleline_4000"
            , TableDefinition = "match_sequence INT, match_index INT, match_value NVARCHAR(4000), group_sequence INT, group_index INT, group_value NVARCHAR(4000), capture_sequence INT, capture_index INT, capture_value NVARCHAR(4000)"
        )]
        public static IEnumerable RegexMatchesSingleline4000(
            [SqlFacet(MaxSize = 4000)]SqlString input
            , [SqlFacet(MaxSize = 4000)]SqlString pattern)
        {
            return GetMatches(input.IsNull ? string.Empty : input.Value
                , pattern.IsNull ? string.Empty : pattern.Value
                , RegexOptions.Singleline);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters")]
        public static void FillRowMatches(object obj, out int matchSequence, out int matchIndex, out string matchValue,
            out int groupSequence, out int groupIndex, out string groupValue,
            out int captureSequence, out int captureIndex, out string captureValue)
        {
            MatchResult result = (MatchResult)obj;
            matchSequence = result.matchSequence;
            matchIndex = result.MatchIndex;
            matchValue = result.MatchValue;
            groupSequence = result.groupSequence;
            groupIndex = result.GroupIndex;
            groupValue = result.GroupValue;
            captureSequence = result.captureSequence;
            captureIndex = result.CaptureIndex;
            captureValue = result.CaptureValue;
        }

        /// <summary>
        /// Generate a list of Match/Group/Capture tuples represented using the
        /// MatchResult struct populated from the regex matches.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        private static List<MatchResult> GetMatches(string input, string pattern, RegexOptions lineOption)
        {
            List<MatchResult> result = new List<MatchResult>();
            int matchSequence = 0;
            int groupSequence = 0;
            int captureSequence = 0;
            foreach (Match m in Regex.Matches(input, pattern, lineOption))
            {
                if (m.Groups.Count < 1)
                    result.Add(new MatchResult(matchSequence, m.Index, m.Value, -1, -1, string.Empty, -1, -1, string.Empty));
                else
                {
                    groupSequence = 0;
                    foreach (Group g in m.Groups)
                    {
                        if (g.Captures.Count < 1)
                            result.Add(new MatchResult(matchSequence, m.Index, m.Value,
                                groupSequence, g.Index, g.Value, -1, -1, string.Empty));
                        else
                        {
                            foreach (Capture c in m.Groups)
                            {
                                result.Add(new MatchResult(matchSequence, m.Index, m.Value,
                                    groupSequence, g.Index, g.Value,
                                    captureSequence, c.Index, c.Value));

                                captureSequence += 1;
                            }
                        }

                        groupSequence += 1;
                    }
                }

                matchSequence += 1;
            }

            return result;
        }

        internal struct MatchResult
        {
            private int _matchSequence;
            public int matchSequence
            {
                get
                {
                    return this._matchSequence;
                }
            }

            private int _matchIndex;
            public int MatchIndex
            {
                get
                {
                    return this._matchIndex;
                }
            }

            private string _matchValue;
            public string MatchValue
            {
                get
                {
                    return this._matchValue;
                }
            }

            private int _groupSequence;
            public int groupSequence
            {
                get
                {
                    return this._groupSequence;
                }
            }

            private int _groupIndex;
            public int GroupIndex
            {
                get
                {
                    return this._groupIndex;
                }
            }

            private string _groupValue;
            public string GroupValue
            {
                get
                {
                    return this._groupValue;
                }
            }

            private int _captureSequence;
            public int captureSequence
            {
                get
                {
                    return this._captureSequence;
                }
            }

            private int _captureIndex;
            public int CaptureIndex
            {
                get
                {
                    return this._captureIndex;
                }
            }

            private string _captureValue;
            public string CaptureValue
            {
                get
                {
                    return this._captureValue;
                }
            }

            public MatchResult(
                int matchSequence, int matchIndex, string matchValue,
                int groupSequence, int groupIndex, string groupValue,
                int captureSequence, int captureIndex, string captureValue)
            {
                this._matchSequence = matchSequence;
                this._matchIndex = matchIndex;
                this._matchValue = matchValue;
                this._groupSequence = groupSequence;
                this._groupIndex = groupIndex;
                this._groupValue = groupValue;
                this._captureSequence = captureSequence;
                this._captureIndex = captureIndex;
                this._captureValue = captureValue;
            }
        }

        [SqlFunction(
            FillRowMethodName = "FillRowSplit"
            , IsDeterministic = true
            , IsPrecise = true
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_split"
            , TableDefinition = "position INT, element NVARCHAR(MAX)"
        )]
        public static IEnumerable RegexSplit(
            [SqlFacet(MaxSize = -1)]SqlString input
            , [SqlFacet(MaxSize = -1)]SqlString pattern)
        {
            return SplitString(input.IsNull ? string.Empty : input.Value
                , pattern.IsNull ? string.Empty : pattern.Value
                , RegexOptions.None);
        }

        [SqlFunction(
            FillRowMethodName = "FillRowSplit"
            , IsDeterministic = true
            , IsPrecise = true
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_split_singleline"
            , TableDefinition = "position INT, element NVARCHAR(MAX)"
        )]
        public static IEnumerable RegexSplitSingleline(
            [SqlFacet(MaxSize = -1)]SqlString input
            , [SqlFacet(MaxSize = -1)]SqlString pattern)
        {
            return SplitString(input.IsNull ? string.Empty : input.Value
                , pattern.IsNull ? string.Empty : pattern.Value
                , RegexOptions.Singleline);
        }

        [SqlFunction(
            FillRowMethodName = "FillRowSplit"
            , IsDeterministic = true
            , IsPrecise = true
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_split_4000"
            , TableDefinition = "position INT, element NVARCHAR(4000)"
        )]
        public static IEnumerable RegexSplit4000(
            [SqlFacet(MaxSize = 4000)]SqlString input
            , [SqlFacet(MaxSize = 4000)]SqlString pattern)
        {
            return SplitString(input.IsNull ? string.Empty : input.Value
                , pattern.IsNull ? string.Empty : pattern.Value
                , RegexOptions.None);
        }

        [SqlFunction(
            FillRowMethodName = "FillRowSplit"
            , IsDeterministic = true
            , IsPrecise = true
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_split_singleline_4000"
            , TableDefinition = "position INT, element NVARCHAR(4000)"
        )]
        public static IEnumerable RegexSplitSingleline4000(
            [SqlFacet(MaxSize = 4000)]SqlString input
            , [SqlFacet(MaxSize = 4000)]SqlString pattern)
        {
            return SplitString(input.IsNull ? string.Empty : input.Value
                , pattern.IsNull ? string.Empty : pattern.Value
                , RegexOptions.Singleline);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters")]
        public static void FillRowSplit(object obj, out int position, out string element)
        {
            SplitResult result = (SplitResult)obj;
            position = result.position;
            element = result.element;
        }

        private static List<SplitResult> SplitString(string input, string pattern, RegexOptions lineOption)
        {
            List<SplitResult> result = new List<SplitResult>();

            string[] splitList = Regex.Split(input, pattern, lineOption);
            int position = 0;

            foreach (string s in splitList)
            {
                result.Add(new SplitResult(position, s));
                position += 1;
            }

            return result;
        }

        internal struct SplitResult
        {
            private int _position;
            public int position
            {
                get
                {
                    return this._position;
                }
            }

            private string _element;
            public string element
            {
                get
                {
                    return this._element;
                }
            }

            public SplitResult(int position, string element)
            {
                this._position = position;
                this._element = element;
            }
        }

        [SqlFunction(
            FillRowMethodName = "FillRowNames"
            , IsDeterministic = true
            , IsPrecise = true
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_group_names"
            , TableDefinition = "group_sequence INT, group_name NVARCHAR(MAX)"
        )]
        public static IEnumerable RegexGroupNames([SqlFacet(MaxSize = -1)]SqlString pattern)
        {
            return GetNames(pattern.IsNull ? string.Empty : pattern.Value);
        }

        [SqlFunction(
            FillRowMethodName = "FillRowNames"
            , IsDeterministic = true
            , IsPrecise = true
            , DataAccess = DataAccessKind.None
            , SystemDataAccess = SystemDataAccessKind.None
            , Name = "regex_group_names_4000"
            , TableDefinition = "group_sequence INT, group_name NVARCHAR(4000)"
        )]
        public static IEnumerable RegexGroupNames4000([SqlFacet(MaxSize = 4000)]SqlString pattern)
        {
            return GetNames(pattern.IsNull ? string.Empty : pattern.Value);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters")]
        public static void FillRowNames(object obj, out int groupSequence, out string groupName)
        {
            GroupResult result = (GroupResult)obj;
            groupSequence = result.groupSequence;
            groupName = result.groupName;
        }

        private static List<GroupResult> GetNames(string pattern)
        {
            List<GroupResult> result = new List<GroupResult>();

            Regex r = new Regex(pattern);

            int[] groupNumbers = r.GetGroupNumbers();

            foreach (int i in groupNumbers)
            {
                result.Add(new GroupResult(i, r.GroupNameFromNumber(i)));
            }

            return result;
        }

        internal struct GroupResult
        {
            private int _groupSequence;
            public int groupSequence
            {
                get
                {
                    return this._groupSequence;
                }
            }

            private string _groupName;
            public string groupName
            {
                get
                {
                    return this._groupName;
                }
            }

            public GroupResult(int groupSequence, string groupName)
            {
                this._groupSequence = groupSequence;
                this._groupName = groupName;
            }
        }
    }
}
